from sklearn.model_selection  import train_test_split
from BigDataWeb.algorithm import Algorithm
import numpy as np
from keras.models import Sequential
from keras.layers import Dense
from matplotlib import pyplot as plt
from multiprocessing import Process, Manager


def runBinaryClassifierModel(data):
    np.random.seed(1986)
    plt.rcParams["font.sans-serif"] = ["SimHei"]
    plt.rcParams['axes.unicode_minus'] = False
    
    hidden_layer_units = data["hidden_layer_units"]
    x = data["x"]
    hidden_layer_count = data["hidden_layer_count"]
    loss = data["loss"]
    optimizer = data["optimizer"]
    train_x = data["train_x"]
    train_y = data["train_y"]
    test_x = data["test_x"]
    test_y = data["test_y"]
    epochs = data["epochs"]
    batch_size = data["batch_size"]
    chart_path = data["chart_path"]
    predict_input_values = data["predict_input_values"]
    
    # 构建模型
    model = Sequential()
    model.add(Dense(units=hidden_layer_units[0], input_dim=x.shape[1], activation='relu'))
    data["model_hidden_layers_code"] = "model.add(Dense(units=%d, input_dim=x.shape[1], activation=\'relu\'))\n" % (hidden_layer_units[0])
    for i in range(1, hidden_layer_count):
        model.add(Dense(units=hidden_layer_units[i], activation='relu'))
        data["model_hidden_layers_code"] += "model.add(Dense(units=%d, activation=\'relu\'))\n" % (hidden_layer_units[i])
    # 去掉最后一个换行字符
    data["model_hidden_layers_code"] = data["model_hidden_layers_code"][:-1] 
    model.add(Dense(units=1, activation='sigmoid'))
    
    # 打印模型信息
    summary_info = []
    model.summary(print_fn=lambda x:summary_info.append(x))
    data["model_summary"] = "<br/>".join(summary_info)
    
    # 编译并训练模型
    model.compile(loss=loss,
          optimizer=optimizer,
          metrics=['accuracy'])
    history = model.fit(train_x, train_y, epochs=epochs, batch_size=batch_size, verbose=0)
    
    # 绘图
    fig = plt.figure()
    ax = fig.add_subplot(111)
    # 绘制训练过程(准确度) 
    ax.plot(history.history["acc"])
    ax.set_title("模型准确度")
    ax.set_xlabel("epoch")
    ax.set_ylabel("accuracy")
    fig.savefig("%s/accuracy.png" % (chart_path))
    ax.cla()
    # 绘制训练过程(损失函数)
    ax.plot(history.history["loss"])
    ax.set_title("模型损失函数")
    ax.set_xlabel("epoch")
    ax.set_ylabel("loss_value")
    fig.savefig("%s/loss_value.png" % (chart_path))
    ax.cla()
    # 评分
    data["train_score"] = model.evaluate(train_x, train_y, verbose=0)[1]
    data["test_score"] = model.evaluate(test_x, test_y, verbose=0)[1]
    # 预测
    predict_x = np.array(predict_input_values)
    data["predict_output_values"] = model.predict_classes(predict_x).tolist()
    data["predict_output_probas"] = model.predict_proba(predict_x).tolist()


class BinaryClassifier(Algorithm):
    # 输出字段(即类别字段)
    output_field_name = ""
    # 训练及测试样本比例
    train_size = 0.8
    test_size = 0.2
    # 待预测数据
    predict_input_values = []
    predict_output_values = []
    predict_output_probas = []
    # 评分(0~1)
    train_score = 0.0
    test_score = 0.0
    # 模型概要
    model_summary = ""
    # 隐藏层层数
    hidden_layer_count = 2
    # 各隐藏层中神经元个数
    hidden_layer_units = [16, 8]
    # 优化函数
    optimizer = "adam"
    # 训练周期
    epochs = 50
    # 批次大小
    batch_size = 32
    # 模型隐藏层对应的代码
    model_hidden_layers_code = ""
    # 输入/输出,其中x=train_x+test_x,y=train_y+test_y
    x = None
    y = None
    # 训练集
    train_x = None
    train_y = None
    # 测试集
    test_x = None
    test_y = None
    # 模型训练过程历史信息
    history = None
    # 损失函数
    loss = ""
    
    def __init__(self):
        Algorithm.__init__(self)
        self.algorithm_name = "深度学习-二分类"
        self.ipynb_template_name = "binary_classifier-template.ipynb"
        self.loss = "binary_crossentropy"
    
    def setOutPutFieldName(self, output_field_name):
        # 区分输出
        self.output_field_name = output_field_name
        self.outputs = self.data_source[self.output_field_name].values
        
    def transferXAndY(self):
        # list->np.array
        self.x = np.array(self.inputs)
        self.y = np.array(self.outputs)
    
    def implent(self):
        # 执行算法
        self.transferXAndY()
        # 拆分训练集和测试集
        (self.train_x, self.test_x, self.train_y, self.test_y) = train_test_split(self.x, self.y, train_size=self.train_size, test_size=self.test_size)
        with Manager() as manager:
            data = manager.dict()
            data["hidden_layer_units"] = self.hidden_layer_units
            data["x"] = self.x
            data["hidden_layer_count"] = self.hidden_layer_count
            data["loss"] = self.loss
            data["optimizer"] = self.optimizer
            data["train_x"] = self.train_x
            data["train_y"] = self.train_y
            data["test_x"] = self.test_x
            data["test_y"] = self.test_y
            data["epochs"] = self.epochs
            data["batch_size"] = self.batch_size
            data["chart_path"] = self.chart_path
            data["predict_input_values"] = self.predict_input_values
            
            process = Process(target=runBinaryClassifierModel, args=(data,))
            process.start()
            process.join()
            
            self.model_hidden_layers_code = data["model_hidden_layers_code"]
            self.model_summary = data["model_summary"]
            self.train_score = data["train_score"]
            self.test_score = data["test_score"]
            self.predict_output_values = data["predict_output_values"]
            self.predict_output_probas = data["predict_output_probas"]
        
    def prepareIpynbItems(self):
        Algorithm.prepareIpynbItems(self)
        self.ipynb_items["#output_field_name#"] = self.output_field_name
        self.ipynb_items["#train_size#"] = self.train_size
        self.ipynb_items["#test_size#"] = self.test_size
        self.ipynb_items["#predict_input_values#"] = self.predict_input_values
        self.ipynb_items["#optimizer#"] = self.optimizer
        self.ipynb_items["#epochs#"] = self.epochs
        self.ipynb_items["#batch_size#"] = self.batch_size
        self.ipynb_items["#model_hidden_layers_code#"] = self.model_hidden_layers_code
